﻿using InCom.Core.TerminalSend.MappedFile;
using InCom.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace InCom.Core.TerminalSend.MappedFile
{
    /// <summary>
    /// 实时数据发送持久化,确保每条数据都能推送到需要的节点(类型为RealDataCalculate)
    /// </summary>
    /// <remarks>
    /// 1.按天来进行数据发送文件持久化,即每天生成一个文件 ,一个数据节点创建 一个
    /// 2.当天进行交接的时候,创建下一天的文件.
    /// 3.开辟一个内存映射文件,用于保存每个文件发送,读取的记录状态
    /// 4.文件管理 ,将过期的文件删除,比如今天删除昨天已经发送完的文件
    /// </remarks>
    public class RealDataSendPersistence
    {

        /// <summary>
        /// 实时数据发送持久化管理
        /// </summary>
        public RealDataSendPersistence()
        {
            //使用磁盘文件映射
            Factory.Instance.Access = MemoryMappendFromFileOperater.Instance.AccessorView;
            CreateMappEntitys();
            //初始化线程
            InitThread();
        }


        #region 持久化状态数据池
        /// <summary>
        /// 持久化状态数据,内存映射,一个节点一个持久化文件
        /// </summary>
        private Queue<PersistenceStatus> statusQueue = new Queue<PersistenceStatus>();



        /// <summary>
        /// 创建固定持久化状态数据
        /// </summary>
        protected void CreateMappEntitys()
        {
            //初始化持久化状态数据
            for (int i = 1; i <= 200; i++)
            {
                var item = new PersistenceStatus();
                if (item.FileNumber == 0)
                {
                    item.FileNumber = i;
                }
                //初始化末完成队列
                if (!item.IsComplated)
                {
                    //完成
                    if (FileComplete(item))
                    {
                        Push(item);
                    }
                    else
                    {
                        //添加到没有完成,没有使用的对像中
                        lock (ReissueQueue)
                        {
                            ReissueQueue.Add(item);
                        }
                    }
                }
                else
                {

                    Push(item);
                }
            }

        }

        /// <summary>
        /// 还池或者向池中添加对像
        /// </summary>
        /// <param name="obj"></param>
        protected void Push(PersistenceStatus obj)
        {
            obj.TagName = string.Empty;
            obj.DiskFileName = string.Empty;
            obj.CurrentIndex = 0;
            obj.UserFlag = string.Empty;
            //设置补发状态为null
            if (obj.ReissueTagBuff != null)
            {
                obj.ReissueTagBuff = null;
            }
            lock (statusQueue)
            {
                statusQueue.Enqueue(obj);
            }
        }

        /// <summary>
        /// 还池或者向池中添加对像
        /// </summary>
        /// <param name="obj"></param>
        protected PersistenceStatus Pop()
        {
            lock (statusQueue)
            {
                return statusQueue.Count > 0 ? statusQueue.Dequeue() : null;
            }
        }
        #endregion


        #region 线程管理

        /// <summary>
        /// 补发线程管理,因为每一个补发持久化对像,都有可能阻塞,所以对每个补发节点采用了一个单独立的线程
        /// </summary>
        internal Dictionary<string, TaskEx> ReissueTasks = new Dictionary<string, TaskEx>();

        /// <summary>
        /// 初始化2个线程 
        /// 1,状态检查线程
        /// 2.补发线程,每个数据持久化补发队像都创建一个补发线程,补发完成后将其设置为可
        /// </summary>
        protected void InitThread()
        {
            TaskCanRun = true;
            TaskEx taskCheckStatus = new TaskEx(CheckStatus) { AutoReRun = false, ThreadName = "实时数据持久化状态检查线程", StartTime = DateTime.Now };
            taskCheckStatus.Start();

        }

        #endregion

        #region 公共属性
        /// <summary>
        /// 时实时数,发送记录文件位置
        /// </summary>
        private string readDataDic = AppDomain.CurrentDomain.BaseDirectory + "RealDataPersistence/";
        /// <summary>
        /// 时实时数,发送记录文件位置
        /// </summary>
        public string ReadDataDic
        {
            get { return readDataDic; }
            protected set { readDataDic = value; }
        }

        /// <summary>
        /// 正在使用的对像
        /// </summary>
        protected ConcurrentDictionary<string, PersistenceStatus> dic = new ConcurrentDictionary<string, PersistenceStatus>();



        /// <summary>
        /// 补发队列,当数据没有发送完成时加入到补发队列中,
        /// </summary>
        protected List<PersistenceStatus> ReissueQueue = new List<PersistenceStatus>();

        /// <summary>
        /// 是否可继续运行
        /// </summary>
        public bool TaskCanRun { get; set; }

        #endregion


        #region 公共写文件数据方法
        /// <summary>
        /// 写数据
        /// </summary>
        /// <param name="buff">需要写入的数据</param>
        /// <returns>节点名</returns>
        public bool Write(byte[] buff, string TagName)
        {
            string key = GetKey(TagName);
            //持久化数据对像
            var per = GetPersistenceStatus(key, TagName);
            if (per != null && per.FileReadWrite != null)
            {
                per.FileReadWrite.Write(buff);
                //最后动作时间
                per.LastActionTime = DateTime.Now;
                return true;
            }
            return false;
        }
        /// <summary>
        /// 写数据
        /// </summary>
        /// <param name="buff">需要写入的数据</param>
        /// <param name="key">key</param>
        /// <param name="TagName">标识名</param>
        /// <returns></returns>
        public bool Write(byte[] buff, string key, string TagName, bool IsStrkey)
        {
            var per = GetPersistenceStatus(key, TagName);
            if (per != null && per.FileReadWrite != null)
            {
                per.FileReadWrite.Write(buff);
                //最后动作时间
                per.LastActionTime = DateTime.Now;
                return true;
            }
            return false;
        }

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="len">一次读取数据长度</param>
        /// <returns></returns>
        public byte[] Read(string TagName, int len = 2048)
        {
            string key = GetKey(TagName);
            //持久化数据对像
            var per = GetPersistenceStatus(key, TagName);

            return Read(per, len);
        }

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="len">一次读取数据长度</param>
        /// <param name="key">标识</param>
        /// <param name="IsStrkey">指定的标识</param>
        /// <returns>返回对像读取数据</returns>
        public byte[] Read(string key, bool IsStrkey, int len = 2048)
        {
            //持久化数据对像
            var per = GetPersistenceStatus(key);
            if (per == null)
            {
                return new byte[0];
            }
            return Read(per, len);
        }

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="len">一次读取数据长度</param>
        /// <returns></returns>
        public byte[] Read(PersistenceStatus per, int len = 2048)
        {
            if (per.FileReadWrite != null)
            {
                //当数据有有效长度时才进行读取
                //if (per.FileReadWrite.CureentWrite > per.CurrentIndex)
                //{ }
                byte[] result = per.FileReadWrite.Read(len);
                //设置前已经读取的索引
                per.CurrentIndex = per.FileReadWrite.CurrentRead;
                //最后动作时间
                per.LastActionTime = DateTime.Now;
                return result;

            }

            return new byte[0];


        }


        /// <summary>
        /// 返回标识key
        /// </summary>
        /// <param name="TagName">数据key</param>
        /// <returns></returns>
        public string GetKey(string TagName)
        {
            //产生key
            string key = string.Format("{0},{1}", TagName, DateTime.Now.ToString("yyyy-MM-dd"));
            return key;
        }


        /// <summary>
        /// 获取持久化数据对像
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        protected PersistenceStatus GetPersistenceStatus(string key, string tagName)
        {
            PersistenceStatus per = null;
            dic.TryGetValue(key, out per);
            //每天添加一个节点
            if (per == null)
            {
                per = PersistenceAnalyse(per);
                if (per == null)
                {
                    throw new Exception("超过指定的节点数量200都已经使用完,");
                }
                lock (dic)
                {
                    if (!dic.TryAdd(key, per))
                    {
                        per = dic[key];
                    }
                }
                #region 修改值

                per.CurrentIndex = 0;
                per.DiskFileName = GetFileName(DateTime.Now, tagName);
                //创建写入文件对像
                if (per.FileReadWrite != null)
                {
                    per.FileReadWrite.Dispose();
                }
                per.FileReadWrite = new FileStreamWriteRead(per.DiskFileName);
                per.IsComplated = false;
                per.TagName = tagName;
                per.LastActionTime = DateTime.Now;
                per.Summary = string.Format("节点{0},时间:{1},的实时数据持久化状态维护对像", tagName, DateTime.Now);
                //重新设置用户key
                per.UserFlag = key;
                #endregion
            }
            return per;
        }
        /// <summary>
        /// 持久化对像是否完成分析
        /// </summary>
        /// <param name="per"></param>
        /// <returns></returns>
        protected PersistenceStatus PersistenceAnalyse(PersistenceStatus per)
        {
            #region 获取一个有效的持久化对像

            int count = statusQueue.Count;
            for (int n = 0; n < count; n++)
            {
                per = Pop();
                if (per != null)
                {
                    //状态是没有完成
                    if (!per.IsComplated)
                    {
                        //查看当前状态是否确实没有完成,如果完成则跳过循环
                        if (FileComplete(per))
                        {
                            //这里如果不进行跳过则可以把所有没有完成需要补发的对像加入到集合中.
                            break;
                        }
                        //添加到没有完成,没有使用的对像中
                        lock (ReissueQueue)
                        {
                            ReissueQueue.Add(per);
                        }

                    }
                    else
                    {
                        //完成状态直接返回
                        break;
                    }

                }
            }

            #endregion

            return per;
        }

        /// <summary>
        /// 获取持久化数据对像
        /// </summary>
        /// <param name="key">持久化数据对像 标识</param>
        /// <returns>返回获取的对像,没有找到返回null</returns>
        public PersistenceStatus GetPersistenceStatus(string key)
        {
            PersistenceStatus per = null;
            dic.TryGetValue(key, out per);
            return per;
        }


        #endregion


        #region 判断文件的公共方法
        /// <summary>
        /// 文件是否已经处理完成了
        /// </summary>
        /// <param name="status">持久化状态对像</param>
        public bool FileComplete(PersistenceStatus status)
        {
            //最后数据操作时间大于10分钟
            if (status.LastActionTime.AddMinutes(0.510) < DateTime.Now)
            {
                DateTime oldDay = DateTime.Parse(status.LastActionTime.ToString("yyyy-MM-dd"));
                DateTime curDay = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));

                //如果没有初始化文件写入对像
                if (status.FileReadWrite == null)
                {
                    //1.检查文件是否存在
                    //2.如果文件存在则检查文件长度
                    //3.如果持久化最后读取>=文件长度则表示文件存在
                    if (string.IsNullOrEmpty(status.DiskFileName))
                    {
                        status.IsComplated = true;
                        return true;
                    }
                    //文件不存在
                    if (!System.IO.File.Exists(status.DiskFileName))
                    {
                        status.IsComplated = true;
                        return true;
                    }
                    else
                    {
                        //文件存在
                        System.IO.FileInfo fileinfo = new System.IO.FileInfo(status.DiskFileName);
                        //表示文件读取完成
                        if (status.CurrentIndex >= fileinfo.Length)
                        {
                            status.IsComplated = true;
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }

                }



                //最后数据保存时间,也有10分钟没有操作了
                if (oldDay < curDay && status.FileReadWrite != null &&
                    //status.FileReadWrite.LastSaveTime.AddMinutes(10) < DateTime.Now
                    status.FileReadWrite.LastSaveTime.AddMinutes(21) < DateTime.Now
                    //把所有写入的字节都读取完成了
                    && status.FileReadWrite.CurrentRead >= status.FileReadWrite.CureentWrite
                    )
                {
                    //释放对像
                    status.FileReadWrite.Dispose();
                    status.IsComplated = true;
                    //找到键
                    string key = GetKey(status.TagName);
                    lock (dic)
                    {
                        dic.TryRemove(key, out status);
                    }

                    if (status != null)
                    {
                        Console.WriteLine(string.Format("数据持久化对像{0}操作完成.生命周期结束", status.Summary));
                    }
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 获取节点当前文件名
        /// </summary>
        /// <param name="status">状态数据</param>
        /// <returns></returns>
        public string GetFileName(DateTime date, string tagName)
        {
            string datestr = date.ToString("yyyy-MM-dd");
            string filename = ReadDataDic + tagName + datestr + ".hex";
            return filename;
        }


        /// <summary>
        /// 删除已经完成的文件
        /// </summary>
        public bool DelCompleteFile(PersistenceStatus status)
        {
            //文件完成
            if (!FileComplete(status))
            {
                return false;
            }
            //启是删除
            return DelCompleteFile(status, true);
        }

        /// <summary>
        /// 总是删除记录
        /// </summary>
        /// <param name="status"></param>
        /// <param name="alwaysDel"></param>
        protected bool DelCompleteFile(PersistenceStatus status, bool alwaysDel)
        {

            //当前时间,用于生成文件
            string filename = status.DiskFileName;
            try
            {
                if (System.IO.File.Exists(filename))
                {
                    System.IO.File.Delete(filename);
                    return true;
                }
            }
            catch
            {
            }

            return false;
        }

        #endregion


        #region 状态维护检查已经完成的数据项将自动删除



        /// <summary>
        /// 检查持久化数据完成状态,每个节点一个
        /// </summary>
        protected void CheckStatus()
        {
            while (TaskCanRun)
            {
                try
                {
                    CheckCompleted();
                }
                catch (Exception ex)
                {
                    inCom.Logs.LogProvider.Create().Write(inCom.Logs.EventType.Error,
                        string.Format("incom.api 实时数据持久化发送出现异常:{0}", ex)
                        );

                }
                //30秒检查一次
                System.Threading.Thread.Sleep(30 * 1000);
            }
        }

        /// <summary>
        /// 检查持久化数据是否已经完成,如果发现了没有完成的包则转到补发队列中
        /// </summary>
        public void CheckCompleted()
        {
            //1.检查正在处理的队列,如果正在处理的队列已经完成,则从队列中删除
            foreach (var item in dic.ToArray())
            {
                PersistenceStatus status = null;
                //判断文件是否完成
                if (item.Value.IsComplated || FileComplete(item.Value))
                {

                    lock (dic)
                    {
                        //删除已经完成的对像
                        if (dic.TryRemove(item.Key, out status))
                        {
                            if (status != null)
                            {
                                //删除任务
                                DeleteTask(status);

                                //删除文件 
                                DelCompleteFile(status, true);

                                //还池
                                Push(status);
                            }
                        }
                    }
                }
                else
                {
                    //如果当前正在使用的key的最后操作时间,与当前时间大于24小时,则将当前字典中的变量移除,添加到补发队列中
                    if ((DateTime.Now - item.Value.LastActionTime).TotalHours >= 24)
                    {
                        lock (dic)
                        {
                            //删除已经完成的对像
                            if (dic.TryRemove(item.Key, out status))
                            {
                                if (status != null)
                                {
                                    //删除任务
                                    DeleteTask(status);
                                    //添加到补发队列
                                    ReissueQueue.Add(item.Value);
                                }
                            }
                        }
                    }

                }
            }


            //2.判断补发队列是否已经处理完成,没有完成则将队列进行 补发
            foreach (var item in ReissueQueue.ToArray())
            {
                if (item.IsComplated || FileComplete(item))
                {
                    //从补发队列中删除已经完成的数据并且将数据还池
                    lock (ReissueQueue)
                    {
                        //删除对像
                        ReissueQueue.Remove(item);
                    }
                    //删除任务
                    DeleteTask(item);
                    //还池
                    Push(item);

                }
                else
                {
                    //补发前判断key是否为有效 UserFlag
                    if (string.IsNullOrEmpty(item.UserFlag))
                    {
                        //删除任务
                        DeleteTask(item);
                        //还池
                        Push(item);
                    }
                    else
                    {
                        //1.判断补发字典中是不是已经存在了补发线程,如果没有存在,则创建一个补发线程
                        if (!ReissueTasks.ContainsKey(item.UserFlag))
                        {
                            #region 加入线程控制
                            //线程控制标识
                            var token = new System.Threading.CancellationTokenSource();
                            TaskEx itemTask = new TaskEx(ReissueMethod, item, token);
                            //添加到补发线程对像中
                            ReissueTasks.Add(item.UserFlag, itemTask);
                            //线程控制
                            System.Threading.Tasks.Task.Factory.StartNew(() =>
                                                                             {
                                                                                 string ken = "编号 " + item.FileNumber + item.UserFlag;
                                                                                 itemTask.Start();
                                                                                 try
                                                                                 {
                                                                                     itemTask.Wait(token.Token);
                                                                                 }
                                                                                 catch (AggregateException exagg)
                                                                                 {
                                                                                     if (item.FileReadWrite != null)
                                                                                     {
                                                                                         //关闭文件流读写
                                                                                         item.FileReadWrite.Dispose();
                                                                                         item.FileReadWrite = null;
                                                                                     }
                                                                                     //对每个异常进行处理
                                                                                     exagg.Handle((ex) =>
                                                                                                      {
                                                                                                          Console.WriteLine(ex);
                                                                                                          System.Diagnostics.Trace.WriteLine(ex);
                                                                                                          return true;
                                                                                                      });
                                                                                 }
                                                                                 catch (OperationCanceledException)
                                                                                 {
                                                                                     if (item.FileReadWrite != null)
                                                                                     {
                                                                                         //关闭文件流读写
                                                                                         item.FileReadWrite.Dispose();
                                                                                         item.FileReadWrite = null;
                                                                                     }


                                                                                     string str = string.Format("{0},线程收到取消任务消息.已经完成运行.当前完成状态:{1}", ken, item.IsComplated);
                                                                                     Console.WriteLine(str);
                                                                                     System.Diagnostics.Trace.WriteLine(str);
                                                                                 }
                                                                             });
                            #endregion
                        }
                    }
                }
            }


        }
        /// <summary>
        /// 补发方法,这个方法一般在单个线程中执行 
        /// </summary>
        /// <param name="obj">参数对像</param>
        private void ReissueMethod(object obj)
        {
            PersistenceStatus per = obj as PersistenceStatus;
            //开始进行数据补发
            while (TaskCanRun)
            {
                if (per.IsComplated || (per.FileReadWrite == null && string.IsNullOrEmpty(per.DiskFileName)))
                {
                    return;
                }
                //循环重发
                ReissueDataProcess(per);
            }
        }

        /// <summary>
        /// 具体补发数据
        /// </summary>
        /// <param name="obj">参数对像</param>
        private void ReissueDataProcess(PersistenceStatus per)
        {
            if (per.FileReadWrite == null)
            {
                //创建写入文件对像
                per.FileReadWrite = new FileStreamWriteRead(per.DiskFileName);
                //创建新的补发数据缓冲区
                if (per.ReissueTagBuff == null)
                {
                    //找到发送数据的节点
                    InCom.SyncStatus.SyncPoint point = SyncStatus.SyncPointManager.Instance.FindSyncPoint(per.TagName, SyncStatus.ConnectionModel.Server);
                    per.ReissueTagBuff = new SendTagsBuffer() { SyncPoint = point };
                }
            }
            ////如果补发数据超过26小时则表示过期
            //if(  (DateTime.Now- per.LastActionTime).TotalHours>26)
            //{

            //    return;
            //}


            byte[] bytes = null;
            //下面写发送数据
            if (per.ReissueTagBuff != null)
            {
                //节点无效则进行在线注册节点查找
                if (per.ReissueTagBuff.SyncPoint == null)
                {
                    //找到发送数据的节点
                    per.ReissueTagBuff.SyncPoint = SyncStatus.SyncPointManager.Instance.FindSyncPoint(per.TagName, SyncStatus.ConnectionModel.Server);
                }
                //节点无效则进行配置文件节点查找
                if (per.ReissueTagBuff.SyncPoint == null)
                {
                    //查看节点在不在配置文件中,存在则表示是一个有效节点
                    var que = SyncStatus.SyncStatusConfigManager.Instance.ConfigItems.ToArray().Where(p => p.TagName == per.TagName);
                    //表示节点有效
                    if (que.Count() > 0)
                    {
                        //如果没有找到节点则1分钟后再进行查找
                        System.Threading.Thread.Sleep(1000 * 30);
                        return;
                    }
                    else
                    {
                        //如果节点无效则向下读取,直到读取完成
                        bytes = Read(per);
                    }
                }
                else
                {
                    //重发过程
                    //1.读取一段数据,发送
                    int datalen = 0;
                    var dataLenBytes = Read(per, 4);
                    if (dataLenBytes.Length == 4)
                    {
                        datalen = BitConverter.ToInt32(dataLenBytes, 0);
                    }
                    //读取字节数据
                    bytes = Read(per, datalen);
                    //需要补发的数据才有补发功能,
                    if (per.ReissueTagBuff.SyncPoint.SyncPointType == SyncStatus.SyncPointType.RealDataCalculate)
                    {
                        if (bytes.Length > 2 && bytes[0] == 0x7e && bytes[bytes.Length - 1] == 0x7e)
                        {
                            //发送数据
                            SyncStatus.SyncPointManager.Instance.Send(per.ReissueTagBuff.SyncPoint, bytes, false);
                        }
                        else
                        {
                            if (datalen > 0 && bytes.Length < datalen)
                            {
                                inCom.Logs.LogProvider.Create().Write(inCom.Logs.EventType.Warning, "补发... 读取持久化实时数据发送出错...");
                            }
                        }
                    }
                }
            }


            /////////////////////////////////////////////////////////////////
            //补发完成
            if (bytes != null && bytes.Length < 1)
            {
                //文件已经读取完成
                if (FileComplete(per) || (per.CurrentIndex >= per.FileReadWrite.CurrentRead && per.FileReadWrite.CurrentRead >= per.FileReadWrite.CureentWrite))
                {
                    //如果已经发送完成则删除补发文件
                    Console.WriteLine(string.Format("{0}号补发对像标识{1};补发完成", per.FileNumber, per.UserFlag));
                    //从补发队列中删除已经完成的数据并且将数据还池
                    lock (ReissueQueue)
                    {
                        //删除对像
                        ReissueQueue.Remove(per);
                    }
                    per.IsComplated = true;
                    //释放变量对像
                    per.FileReadWrite.Dispose();
                    //删除补发文件
                    DelCompleteFile(per, true);
                    //设置为null
                    per.FileReadWrite = null;
                    //删除任务
                    DeleteTask(per);
                    //还池
                    Push(per);

                }
            }
            //可能程序补发已经完成了,
            //这里修改主要是保证程序CPU占用不会高
            //判断buff=null and  bytes =null 则关闭当关线程
            if (per.ReissueTagBuff == null || bytes == null)
            {
                per.IsComplated = true;
                if (per.FileReadWrite != null)
                {
                    //释放变量对像
                    per.FileReadWrite.Dispose();
                    //删除补发文件
                    DelCompleteFile(per, true);
                    //设置为null
                    per.FileReadWrite = null;
                }
                //删除任务
                DeleteTask(per);
                //还池
                Push(per);
            }


        }

        /// <summary>
        /// 从线程任务集合中删除任务
        /// </summary>
        /// <param name="item">需要删除的任务持久化对像</param>
        private void DeleteTask(PersistenceStatus item)
        {
            //从补发线程中删除对像
            if (ReissueTasks.ContainsKey(item.UserFlag))
            {
                var tmptesk = ReissueTasks[item.UserFlag];
                lock (ReissueTasks)
                {
                    ReissueTasks.Remove(item.UserFlag);
                }
                if (item.FileReadWrite != null)
                {
                    //关闭文件流读写
                    item.FileReadWrite.Dispose();
                    item.FileReadWrite = null;
                }


                try
                {
                    //关闭线程
                    tmptesk.CanToKen.Cancel();
                }
                catch (AggregateException exagg)
                {
                    //对每个异常进行处理
                    exagg.Handle((ex) =>
                    {
                        Console.WriteLine(ex);
                        System.Diagnostics.Trace.WriteLine(ex);
                        return true;
                    });
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                    System.Diagnostics.Trace.WriteLine(ex);
                }
            }
        }



        #endregion

    }
}
